﻿using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Serialization;

namespace uTILLIty.Rave.Contract.Job
{
  /// <summary>
  /// An NVelocity variable defined in a <see cref="Job"/>
  /// </summary>
  public class MergeVariable
  {
    private static readonly Regex HeaderNameParseRegex = new Regex(@"(?i)(?<name>[^\[]+)\[(?<chars>[^\]]+)]$");

    /// <summary>
    /// Creates a default instance
    /// </summary>
    public MergeVariable()
    {
    }

    /// <summary>
    /// Creates a filled-out instance
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    public MergeVariable(string key, object value)
    {
      Key = key;
	    var stringValue = value as string;
	    if (stringValue != null)
		    StringValue = stringValue;
			else
				Value = value;
    }

    /// <summary>
    /// The key by which the variable is identified
    /// </summary>
    [XmlAttribute("key")]
    public string Key { get; set; }

    /// <summary>
    /// The value of the key
    /// </summary>
    /// <remarks>
    /// Use <seealso cref="StringValue"/> for string-type variables, for which ENV-variable expansion should be performed
    /// </remarks>
    /// <seealso cref="StringValue"/>
    [XmlIgnore]
    public virtual object Value { get; set; }

    /// <summary>
    /// A string representation (used for XML serialization) of the <see cref="Value"/>
    /// </summary>
		/// <remarks>Performs expansion of ENV-Variables. MergeVariables may not be used.</remarks>
    [XmlAttribute("value")]
    public string StringValue
    {
      get { return (Value ?? "").ToString(); }
      set { Value = Environment.ExpandEnvironmentVariables(value); }
    }

    /// <summary>
    /// Wether this instance equals the supplied one by <see cref="Key"/>
    /// </summary>
    public bool Equals(MergeVariable other)
    {
      if (ReferenceEquals(null, other)) return false;
      if (ReferenceEquals(this, other)) return true;
      return Equals(other.Key, Key);
    }

    /// <summary>
    /// Wether this instance equals the supplied one by <see cref="Key"/>
    /// </summary>
    public override bool Equals(object obj)
    {
      if (ReferenceEquals(null, obj)) return false;
      if (ReferenceEquals(this, obj)) return true;
      if (obj.GetType() != typeof(MergeVariable)) return false;
      return Equals((MergeVariable)obj);
    }

    /// <summary>
    /// Returns the hash of the <see cref="Key"/>
    /// </summary>
    /// <returns></returns>
    public override int GetHashCode()
    {
      return Key.GetHashCode();
    }

    /// <summary>
    /// Wether this instance equals the supplied one by <see cref="Key"/>
    /// </summary>
    public static bool operator ==(MergeVariable left, MergeVariable right)
    {
      return Equals(left, right);
    }

    /// <summary>
    /// Wether this instance equals the supplied one by <see cref="Key"/>
    /// </summary>
    public static bool operator !=(MergeVariable left, MergeVariable right)
    {
      return !Equals(left, right);
    }

    /// <summary>
    /// Creates a new instance of either <see cref="MergeVariable"/> or <see cref="ListMergeVariable"/>, depending
    /// on the <paramref name="rawKey"/> format
    /// </summary>
    /// <remarks>
    /// If <paramref name="rawKey"/> ends with square-brackets [], <paramref name="rawValue"/> is interpreted
    /// as a list and split using the split-character supplied between the brackets
    /// </remarks>
    /// <example>
    /// rawKey: users[,]
    /// rawValue: User1,User2,User3
    /// </example>
    public static MergeVariable Create(string rawKey, string rawValue)
    {
      string key;
      char[] splitChars;
      object value = rawValue;

      bool splitValue = ExtractHeadName(rawKey, out key, out splitChars);
      return (!splitValue)
          ? new MergeVariable(key, value)
          : new ListMergeVariable(key, string.IsNullOrEmpty(value.ToString())
                                                                                              ? new string[0]
                                                                                              : value.ToString().Split(splitChars));
    }

    internal static bool ExtractHeadName(string input, out string headName, out char[] splitChars)
    {
      headName = input;
      splitChars = new char[0];
      if (!headName.EndsWith("]"))
        return false;

      var exp = HeaderNameParseRegex;
      var match = exp.Match(headName);
      if (!match.Success)
        return false;

      headName = match.Groups["name"].Value;
      splitChars = match.Groups["chars"].Value.ToArray();
      return true;
    }

  }
}